<template>
  <div class="computed-internals">
    <h2>Vue3 计算属性缓存原理深度解析</h2>

    <div class="explanation">
      <h3>1. 核心机制：dirty标记 + 依赖收集</h3>
      <div class="code-block">
        <pre><code>// Vue3 computed 内部简化实现
class ComputedRefImpl {
  private _value: any
  private _dirty = true  // 脏标记，决定是否需要重新计算
  public readonly effect: ReactiveEffect
  
  constructor(getter, isReadonly) {
    // 创建响应式副作用，用于依赖收集
    this.effect = new ReactiveEffect(getter, () => {
      // 依赖变化时的回调，设置脏标记
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this) // 触发依赖此计算属性的更新
      }
    })
  }
  
  get value() {
    // 依赖收集：将当前计算属性加入依赖图
    trackRefValue(this)
    
    // 只有脏了才重新计算
    if (this._dirty) {
      this._dirty = false
      this._value = this.effect.run() // 执行getter，收集依赖
    }
    
    return this._value // 返回缓存值
  }
}</code></pre>
      </div>
    </div>

    <div class="explanation">
      <h3>2. 依赖收集过程</h3>
      <div class="step">
        <h4>步骤1: 首次访问</h4>
        <ul>
          <li>_dirty = true，需要计算</li>
          <li>执行 effect.run()，运行 getter 函数</li>
          <li>getter 中访问响应式数据，触发依赖收集</li>
          <li>将当前 effect 添加到响应式数据的依赖集合中</li>
          <li>设置 _dirty = false，缓存结果</li>
        </ul>
      </div>

      <div class="step">
        <h4>步骤2: 再次访问（依赖未变）</h4>
        <ul>
          <li>_dirty = false，直接返回 _value</li>
          <li>不执行 getter，不重新计算</li>
          <li>这就是缓存的关键！</li>
        </ul>
      </div>

      <div class="step">
        <h4>步骤3: 依赖变化</h4>
        <ul>
          <li>响应式数据变化，触发 trigger</li>
          <li>执行计算属性的 scheduler 回调</li>
          <li>设置 _dirty = true</li>
          <li>下次访问时重新计算</li>
        </ul>
      </div>
    </div>

    <div class="explanation">
      <h3>3. 实际演示</h3>
      <div class="demo">
        <p>count: {{ count }}</p>
        <p>doubleCount (计算属性): {{ doubleCount }}</p>
        <p>计算次数: {{ computeCount }}</p>
        <p>访问次数: {{ accessCount }}</p>

        <button @click="count++">增加 count</button>
        <button @click="accessDoubleCount">访问 doubleCount (不改变依赖)</button>

        <div class="log">
          <h4>执行日志:</h4>
          <div v-for="log in logs" :key="log.id" class="log-item">
            {{ log.message }}
          </div>
        </div>
      </div>
    </div>

    <div class="explanation">
      <h3>4. 与方法的对比</h3>
      <table>
        <thead>
          <tr>
            <th>特性</th>
            <th>计算属性</th>
            <th>方法</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>缓存</td>
            <td>✅ 有缓存，依赖不变时返回缓存值</td>
            <td>❌ 无缓存，每次调用都执行</td>
          </tr>
          <tr>
            <td>依赖追踪</td>
            <td>✅ 自动追踪依赖，精确更新</td>
            <td>❌ 无依赖追踪</td>
          </tr>
          <tr>
            <td>性能</td>
            <td>✅ 高性能，避免重复计算</td>
            <td>⚠️ 可能有性能问题</td>
          </tr>
          <tr>
            <td>使用场景</td>
            <td>复杂计算，基于响应式数据</td>
            <td>简单操作，事件处理</td>
          </tr>
        </tbody>
      </table>
    </div>

    <div class="explanation">
      <h3>5. 高级特性</h3>
      <div class="advanced">
        <h4>可写计算属性</h4>
        <div class="code-block">
          <pre><code>const fullName = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})</code></pre>
        </div>

        <h4>计算属性的计算属性（链式依赖）</h4>
        <div class="code-block">
          <pre><code>const count = ref(1)
const doubled = computed(() => count.value * 2)
const quadrupled = computed(() => doubled.value * 2)
// quadrupled 依赖 doubled，doubled 依赖 count
// 形成依赖链，任何一个变化都会正确传播</code></pre>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";

// 演示数据
const count = ref(0);
const computeCount = ref(0);
const accessCount = ref(0);
const logs = ref<Array<{ id: number; message: string }>>([]);

let logId = 0;
const addLog = (message: string) => {
  logs.value.push({
    id: logId++,
    message: `${new Date().toLocaleTimeString()}: ${message}`,
  });
  if (logs.value.length > 10) {
    logs.value.shift();
  }
};

const doubleCount = computed(() => {
  computeCount.value++;
  addLog(`计算属性被执行: count = ${count.value}, result = ${count.value * 2}`);
  return count.value * 2;
});

const accessDoubleCount = () => {
  accessCount.value++;
  const value = doubleCount.value;
  addLog(`访问计算属性: 返回缓存值 ${value}`);
};
</script>

<style scoped>
.computed-internals {
  padding: 20px;
  max-width: 1000px;
  margin: 0 auto;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}

.explanation {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  border-left: 4px solid #007bff;
}

.explanation h3 {
  color: #007bff;
  margin-top: 0;
}

.code-block {
  background-color: #2d3748;
  color: #e2e8f0;
  padding: 16px;
  border-radius: 8px;
  overflow-x: auto;
  margin: 16px 0;
}

.code-block pre {
  margin: 0;
  font-family: "Fira Code", "Consolas", monospace;
  font-size: 14px;
  line-height: 1.5;
}

.step {
  margin: 16px 0;
  padding: 16px;
  background-color: white;
  border-radius: 6px;
  border: 1px solid #e1e5e9;
}

.step h4 {
  color: #495057;
  margin-top: 0;
}

.step ul {
  margin: 8px 0;
  padding-left: 20px;
}

.step li {
  margin: 4px 0;
  color: #6c757d;
}

.demo {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.demo button {
  padding: 8px 16px;
  margin: 5px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.demo button:hover {
  background-color: #218838;
}

.log {
  margin-top: 20px;
  padding: 16px;
  background-color: #f8f9fa;
  border-radius: 6px;
  max-height: 200px;
  overflow-y: auto;
}

.log-item {
  font-family: monospace;
  font-size: 12px;
  color: #495057;
  margin: 4px 0;
  padding: 4px;
  background-color: white;
  border-radius: 3px;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 16px 0;
  background-color: white;
}

th,
td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #dee2e6;
}

th {
  background-color: #f8f9fa;
  font-weight: 600;
  color: #495057;
}

.advanced {
  background-color: white;
  padding: 16px;
  border-radius: 6px;
  border: 1px solid #e1e5e9;
}

.advanced h4 {
  color: #6f42c1;
  margin-top: 20px;
}

.advanced h4:first-child {
  margin-top: 0;
}
</style>
